﻿// 2022/4/1: 首个版本
// 2022/4/14: 增加DefaultSize, MinSize, DeprecatedAPI, Signature字段。RelatedModules拆分为必需和可选两部分
// 2022/4/26: 增加[SO]
// 2022/10/11: 增加UsedAPIs字段。简化代码
// 2023/5/5: 增加RelatedPythonTemplates字段
// 2023/5/24: 最小高度缩小至50
// 2023/9/13: Load(zipPath)增加chinese参数
// 2023/10/9: 添加英文注释
// 2024/4/17: 支持API3接口
// 2024/6/25: 不使用Agency
// 2024/6/26: 输出语言代号zh
// 2024/7/22: 新增WebStyleSize

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml;
using System.Text;
using ICSharpCode.SharpZipLib.Zip;
using ASEva;
using ASEva.Utility;
using SO = System.Reflection.ObfuscationAttribute;

namespace CommonSource
{
    [SO]
    public enum HTMLModuleType
    {
        All,
        Window,
        Dialog,
    }

    partial class HTMLCommon
    {
        public static String GetHTMLRoot()
        {
            var root = AgencyLocal.GetAppFilesRoot() + Path.DirectorySeparatorChar + "html";
            if (!Directory.Exists(root)) Directory.CreateDirectory(root);
            return root;
        }

        public static List<HTMLModuleDefinition> LoadDefinitions(HTMLModuleType type)
        {
            var list = new List<HTMLModuleDefinition>();
            foreach (var file in Directory.GetFiles(GetHTMLRoot(), "*.html"))
            {
                var entryName = Path.GetFileNameWithoutExtension(file);
                if (entryName == "index") continue;
                if (entryName.IndexOf('.') >= 0) continue;
                var def = HTMLModuleDefinition.Load(entryName, list);
                if (def == null) def = HTMLModuleDefinition.Create(entryName, HTMLModuleType.Window, list);
                if (type == HTMLModuleType.All || type == def.Type) list.Add(def);
            }
            list.Sort();
            return list;
        }

        public static String[] GetIconExtensions()
        {
            return new String[] { ".bmp", ".png", ".jpg", ".jpeg" };
        }

        public static String FindIconFile(String entryName)
        {
            foreach (var ext in GetIconExtensions())
            {
                var iconFilePath = HTMLCommon.GetHTMLRoot() + Path.DirectorySeparatorChar + entryName + ext;
                if (File.Exists(iconFilePath)) return iconFilePath;
            }
            return null;
        }
    }

    public class HTMLModuleDefinition : IComparable
    {
        public String ZipPath { get; set; }
        public String EntryName { get; set; }
        public HTMLModuleType Type { get; set; }
        public String TitleEnglish { get; set; }
        public String TitleChinese { get; set; }
        public String Version { get; set; }
        public String DependAPI { get; set; }
        public String DeprecatedAPI { get; set; }
        public IntSize DefaultSize { get; set; }
        public IntSize MinSize { get; set; }
        public IntSize WebStyleSize { get; set; }
        public List<String> NecessaryModules { get; set; } // Required / 必需
        public List<String> OptionalModules { get; set; } // Optional / 可选
        public List<String> RelatedPythonTemplates { get; set; }
        public String Signature { get; set; }
        public List<String> UsedAPIs { get; set; }

        public List<String> RelatedModules
        {
            get
            {
                var list = new List<String>();
                if (NecessaryModules != null) list.AddRange(NecessaryModules);
                if (OptionalModules != null) list.AddRange(OptionalModules);
                return list;
            }
        }

        private HTMLModuleDefinition()
        {
            NecessaryModules = new List<string>();
            OptionalModules = new List<string>();
            RelatedPythonTemplates = new List<string>();
            UsedAPIs = new List<string>();
        }

        public static HTMLModuleDefinition Create(String entryName, HTMLModuleType type, List<HTMLModuleDefinition> otherConfigs)
        {
            if (String.IsNullOrWhiteSpace(entryName) || type == HTMLModuleType.All) return null;

            int i = 0;
            String nameEn = null, nameZh = null;
            while (true)
            {
                var indexText = ++i > 1 ? i.ToString() : "";
                var targetEnglishName = "Unnamed " + type.ToString() + (indexText.Length > 0 ? (" " + indexText) : "");
                var targetChineseName = "未命名" + (type == HTMLModuleType.Window ? "窗口" : "对话框") + indexText;
                if (otherConfigs.Exists(c => c.TitleEnglish == targetEnglishName) || 
                    otherConfigs.Exists(c => c.TitleChinese == targetChineseName)) continue;
                nameEn = targetEnglishName;
                nameZh = targetChineseName;
                break;
            }

            var def = new HTMLModuleDefinition();
            def.EntryName = entryName;
            def.Type = type;
            def.TitleEnglish = nameEn;
            def.TitleChinese = nameZh;
            def.Version = "1.0.0";
            def.DependAPI = "";
            def.DeprecatedAPI = "";
            def.DefaultSize = type == HTMLModuleType.Dialog ? new IntSize(700, 500) : new IntSize(400, 300);
            def.WebStyleSize = def.DefaultSize;
            def.MinSize = new IntSize(200, 50);
            def.Signature = "";
            def.Save();
            return def;
        }

        private static void loadCommon(AttributeParser cp, HTMLModuleType type, XmlDocument defXml, HTMLModuleDefinition def)
        {
            var minWidth = Math.Max(200, cp.ParseInt("min_width", 0));
            var minHeight = Math.Max(50, cp.ParseInt("min_height", 0));

            var defaultWidth = cp.ParseInt("default_width", 0);
            if (defaultWidth < 200) defaultWidth = type == HTMLModuleType.Window ? 400 : 700;
            
            var defaultHeight = cp.ParseInt("default_height", 0);
            if (defaultHeight < 50) defaultHeight = type == HTMLModuleType.Window ? 300 : 500;

            var webStyleWidth = cp.ParseInt("web_style_width", 0);
            if (webStyleWidth < 200) webStyleWidth = type == HTMLModuleType.Window ? 400 : 700;
            
            var webStyleHeight = cp.ParseInt("web_style_height", 0);
            if (webStyleHeight < 50) webStyleHeight = type == HTMLModuleType.Window ? 300 : 500;

            var dependAPI = cp.ParseString("depend_api", null);
            if (dependAPI == null) dependAPI = "";

            var deprecatedAPI = cp.ParseString("deprecated_api", null);
            if (deprecatedAPI == null) deprecatedAPI = "";

            var signature = cp.ParseString("signature", null);
            if (signature == null) signature = "";

            var necessaryModules = new Dictionary<String, bool>();
            var optionalModules = new Dictionary<String, bool>();
            foreach (XmlElement rmNode in defXml.DocumentElement.GetElementsByTagName("rm"))
            {
                var moduleClassID = rmNode.InnerText;
                if (String.IsNullOrWhiteSpace(moduleClassID)) continue;

                var optional = rmNode.Attributes["optional"] != null && rmNode.Attributes["optional"].Value == "yes";
                if (optional) optionalModules[moduleClassID] = true;
                else necessaryModules[moduleClassID] = true;
            }

            var relatedPythonTemplates = new List<String>();
            foreach (XmlElement pyNode in defXml.DocumentElement.GetElementsByTagName("py"))
            {
                var templateID = pyNode.InnerText;
                if (String.IsNullOrWhiteSpace(templateID)) continue;

                relatedPythonTemplates.Add(templateID);
            }

            var usedAPIs = new List<String>();
            foreach (XmlElement useApiNode in defXml.DocumentElement.GetElementsByTagName("use_api"))
            {
                usedAPIs.Add(useApiNode.InnerText);
            }

            def.DependAPI = dependAPI;
            def.DeprecatedAPI = deprecatedAPI;
            def.Signature = signature;
            def.DefaultSize = new IntSize(defaultWidth, defaultHeight);
            def.WebStyleSize = new IntSize(webStyleWidth, webStyleHeight);
            def.MinSize = new IntSize(minWidth, minHeight);
            def.NecessaryModules = necessaryModules.Keys.ToList();
            def.OptionalModules = optionalModules.Keys.ToList();
            def.RelatedPythonTemplates = relatedPythonTemplates;
            def.UsedAPIs = usedAPIs;
        }

        public static HTMLModuleDefinition Load(String entryName, List<HTMLModuleDefinition> otherConfigs)
        {
            try
            {
                var defFilePath = HTMLCommon.GetHTMLRoot() + Path.DirectorySeparatorChar + entryName + ".xml";
                if (!File.Exists(defFilePath)) return null;

                var defXml = new XmlDocument();
                defXml.Load(defFilePath);

                var cp = new AttributeParser(defXml.DocumentElement);
                var type = (HTMLModuleType)cp.ParseEnum("type", typeof(HTMLModuleType), HTMLModuleType.Window);
                
                var ver = cp.ParseString("version", "");
                if (String.IsNullOrEmpty(ver)) ver = "1.0.0";

                var nameEn = cp.ParseString("name", null);
                var nameZh = cp.ParseString("name_zh", null);
                if (nameZh == null) nameZh = cp.ParseString("name_ch", null);
                if (nameEn != null && nameEn.Length > 0)
                {
                    if (otherConfigs.Exists(c => c.TitleEnglish == nameEn)) nameEn = null;
                }
                if (nameZh != null && nameZh.Length > 0)
                {
                    if (otherConfigs.Exists(c => c.TitleChinese == nameZh)) nameZh = null;
                }
                if (nameEn == null || nameEn.Length == 0)
                {
                    int i = 0;
                    while (true)
                    {
                        var targetName = "Unnamed " + type.ToString() + (++i > 1 ? (" " + i) : "");
                        if (otherConfigs.Exists(c => c.TitleEnglish == targetName)) continue;
                        nameEn = targetName;
                        break;
                    }
                }
                if (nameZh == null || nameZh.Length == 0)
                {
                    int i = 0;
                    while (true)
                    {
                        var targetName = "未命名" + (type == HTMLModuleType.Window ? "窗口" : "对话框") + (++i > 1 ? i.ToString() : "");
                        if (otherConfigs.Exists(c => c.TitleChinese == targetName)) continue;
                        nameZh = targetName;
                        break;
                    }
                }

                var def = new HTMLModuleDefinition();
                def.EntryName = entryName;
                def.Type = type;
                def.TitleEnglish = nameEn;
                def.TitleChinese = nameZh;
                def.Version = ver;

                loadCommon(cp, type, defXml, def);

                return def;
            }
            catch (Exception) { return null; }
        }

        public static HTMLModuleDefinition Load(String entryName, Version targetVersion, String innerXml, String zipFilePath)
        {
            try
            {
                var defXml = new XmlDocument();
                defXml.LoadXml(innerXml);

                var attribs = defXml.DocumentElement.Attributes;
                var cp = new AttributeParser(defXml.DocumentElement);

                HTMLModuleType type = HTMLModuleType.All;
                if (attribs["type"] == null || !Enum.TryParse<HTMLModuleType>(attribs["type"].Value, out type)) return null;

                Version ver;
                if (attribs["version"] == null || !System.Version.TryParse(attribs["version"].Value, out ver)) return null;
                if (ver != targetVersion) return null;

                if (attribs["name"] == null || (attribs["name_zh"] == null && attribs["name_ch"] == null)) return null;

                var nameEn = attribs["name"].Value;
                var nameZh = attribs["name_zh"] == null ? attribs["name_ch"].Value : attribs["name_zh"].Value;

                var def = new HTMLModuleDefinition();
                def.ZipPath = zipFilePath;
                def.EntryName = entryName;
                def.Type = type;
                def.TitleEnglish = nameEn;
                def.TitleChinese = nameZh;
                def.Version = ver.ToString(3);

                loadCommon(cp, type, defXml, def);

                return def;
            }
            catch (Exception) { return null; }
        }

        public static HTMLModuleDefinition Load(String zipPath, out String error, bool? chinese = null)
        {
            error = null;

            bool zh = false;
            if (chinese == null) zh = AgencyLocal.GetAppLanguage() == Language.Chinese;
            else zh = chinese.Value;

            String xmlString = null;
            Version version = null;
            String entryName = null;
            try
            {
                var zipFileName = Path.GetFileNameWithoutExtension(zipPath);
                var comps = zipFileName.Split('-');
                if (comps.Length < 4)
                {
                    error = String.Format(zh ? "zip文件名格式无效 ({0})" : "Invalid zip file name format ({0})", zipFileName);
                    return null;
                }

                version = new Version(Convert.ToInt32(comps[comps.Length - 3]), Convert.ToInt32(comps[comps.Length - 2]), Convert.ToInt32(comps[comps.Length - 1]));
                entryName = String.Join('-', comps.AsSpan(0, comps.Length - 3).ToArray());

                using (var s = new ZipInputStream(File.OpenRead(zipPath)))
                {
                    ZipEntry theEntry = null;
                    while ((theEntry = s.GetNextEntry()) != null)
                    {
                        var directoryName = Path.GetDirectoryName(theEntry.Name);
                        var fileName = Path.GetFileName(theEntry.Name);
                        if (!String.IsNullOrEmpty(fileName) && String.IsNullOrEmpty(directoryName))
                        {
                            if (fileName == entryName + ".xml")
                            {
                                var fileData = new byte[s.Length];
                                s.Read(fileData, 0, (int)s.Length);
                                if (fileData[0] == 0xef && fileData[1] == 0xbb && fileData[2] == 0xbf) fileData = fileData.AsSpan(3).ToArray();

                                xmlString = Encoding.UTF8.GetString(fileData);
                                break;
                            }
                        }
                    }
                }
            }
            catch (Exception) {}

            if (String.IsNullOrEmpty(xmlString))
            {
                error = zh ? "解压并获取xml数据失败" : "Failed to decompress and query xml data";
                return null;
            }

            var defXml = new XmlDocument();
            defXml.LoadXml(xmlString);

            var attribs = defXml.DocumentElement.Attributes;
            var cp = new AttributeParser(defXml.DocumentElement);
            
            if (attribs["type"] == null)
            {
                error = zh ? "无类型信息" : "No type info";
                return null;
            }
            if (attribs["version"] == null)
            {
                error = zh ? "无版本信息" : "No version info";
                return null;
            }
            if (attribs["name"] == null)
            {
                error = zh ? "无英文名" : "No English name";
                return null;
            }
            if (attribs["name_zh"] == null && attribs["name_ch"] == null)
            {
                error = zh ? "无中文名" : "No Chinese name";
                return null;
            }

            var type = Enum.Parse<HTMLModuleType>(attribs["type"].Value);
            var nameEn = attribs["name"].Value;
            var nameZh = attribs["name_zh"] == null ? attribs["name_ch"].Value : attribs["name_zh"].Value;

            var ver = attribs["version"].Value;
            if (ver != version.ToString(3))
            {
                error = zh ? "zip包文件名版本与内部xml版本不一致" : "Version by zip filename doesn't match the version by xml inside";
                return null;
            }

            var def = new HTMLModuleDefinition();
            def.ZipPath = zipPath;
            def.EntryName = entryName;
            def.Type = type;
            def.TitleEnglish = nameEn;
            def.TitleChinese = nameZh;
            def.Version = ver;

            loadCommon(cp, type, defXml, def);

            return def;
        }

        public void Save()
        {
            var xml = Xml.Create("root");
            var cw = new AttributeWriter(xml.DocumentElement);
            cw.WriteString("name", TitleEnglish);
            cw.WriteString("name_zh", TitleChinese);
            cw.WriteString("type", Type.ToString());
            cw.WriteString("version", Version);
            cw.WriteString("depend_api", DependAPI);
            cw.WriteString("deprecated_api", DeprecatedAPI);
            cw.WriteString("signature", Signature);
            cw.WriteInt("default_width", DefaultSize.Width);
            cw.WriteInt("default_height", DefaultSize.Height);
            cw.WriteInt("web_style_width", WebStyleSize.Width);
            cw.WriteInt("web_style_height", WebStyleSize.Height);
            cw.WriteInt("min_width", MinSize.Width);
            cw.WriteInt("min_height", MinSize.Height);

            foreach (var relatedModule in NecessaryModules)
            {
                var elem = xml.DocumentElement.AppendChild(xml.CreateElement("rm")) as XmlElement;
                elem.Attributes.Append(xml.CreateAttribute("optional")).Value = "no";
                elem.InnerText = relatedModule;
            }
            foreach (var relatedModule in OptionalModules)
            {
                var elem = xml.DocumentElement.AppendChild(xml.CreateElement("rm")) as XmlElement;
                elem.Attributes.Append(xml.CreateAttribute("optional")).Value = "yes";
                elem.InnerText = relatedModule;
            }
            foreach (var pythonTemplate in RelatedPythonTemplates)
            {
                xml.DocumentElement.AppendChild(xml.CreateElement("py")).InnerText = pythonTemplate;
            }

            foreach (var api in UsedAPIs)
            {
                xml.DocumentElement.AppendChild(xml.CreateElement("use_api")).InnerText = api;
            }

            var defFilePath = HTMLCommon.GetHTMLRoot() + Path.DirectorySeparatorChar + EntryName + ".xml";
            xml.Save(defFilePath);
        }

        public int CompareTo(object obj)
        {
            var lang = AgencyLocal.GetAppLanguage();
            bool chinese = false;
            if (lang == Language.Invalid) chinese = DefaultCompareChinese;
            else chinese = lang == Language.Chinese;
            if (chinese) return TitleChinese.CompareTo((obj as HTMLModuleDefinition).TitleChinese);
            else return TitleEnglish.CompareTo((obj as HTMLModuleDefinition).TitleEnglish);
        }

        public static bool DefaultCompareChinese { get; set; }
    }
}
